/*
	mips32r2.c
	
	Methods used by MIPS32r2
	
	Meng Sun(c) leon.meng.sun@gmail.com
	Source code released under LGPLv3
	https://www.gnu.org/licenses/lgpl-3.0.txt
*/

#include "mips32r2.h"

static void callback (*sw0ISRCallback)(void) = 0x00000000;

static void callback (*sw1ISRCallback)(void) = 0x00000000;

static void callback (*i8259aISRCallback)(void) = 0x00000000;

static void callback (*tlbLoadStoreISRCallback)(reg8b, reg32b, reg32b) = NULL;

static void callback (*memorySyscallISRCallback)(reg32b, uint32*, SyscallDescriptor*, SyscallReturnDescriptor*) = NULL;

static void callback (*procSyscallISRCallback)(reg32b, uint32*, SyscallDescriptor*, SyscallReturnDescriptor*) = NULL;

static void callback mips32r2SW0InterruptService(void){

	reg32b cp0CauseVal = 0x00000000;
	
	ReadCP0Value(cp0CauseVal, 13, 0);
	cp0CauseVal &= 0x00000e00; /*clear sw0 bit in cp0 cause*/
	WriteCP0Value(cp0CauseVal, 13, 0);

	return;
}

static void callback mips32r2SW1InterruptService(void){

	reg32b cp0CauseVal = 0x00000000;

	ReadCP0Value(cp0CauseVal, 13, 0);
	cp0CauseVal &= 0x00000d00; /*clear sw1 bit in cp0 cause*/
	WriteCP0Value(cp0CauseVal, 13, 0);

	return;
}

void mips32r2Init(void){

	reg32b val = 0x00000000;
	
	ReadCP0Value(val, 23, 0); /*kernel mode*/
	val &= 0xbfffffff;
	WriteCP0Value(val, 23, 0);
	
	ReadCP0Value(val, 12, 0);
	val &= 0xffffffe1;
	WriteCP0Value(val, 12, 0);
	
	ReadCP0Value(val, 12, 0); /*compatible interrupt mode*/
	val |= 0x0040ff00;
	WriteCP0Value(val, 12, 0);
	
	ReadCP0Value(val, 13, 0);
	val |= 0x00800000;
	WriteCP0Value(val, 13, 0);
	
	setSW0ISRCallback(&mips32r2SW0InterruptService);
	setSW1ISRCallback(&mips32r2SW1InterruptService);

	return;
}

void mips32r2EnableInterrupt(void){

	reg32b val = 0x00000000;

	ReadCP0Value(val, 12, 0); /*enable interrupt*/
	val |= 0x00000001;
	WriteCP0Value(val, 12, 0);
	
	return;
}

void setTLBLoadStoreISRCallback(void callback (*funcptr)(reg8b, reg32b, reg32b)){
	tlbLoadStoreISRCallback = funcptr;
	return;
}

void setSW0ISRCallback(void callback (*funcptr)(void)){

	sw0ISRCallback = funcptr;
	return;
}

void setSW1ISRCallback(void callback (*funcptr)(void)){

	sw1ISRCallback = funcptr;
	return;
}


void seti8259aISRCallback(void callback (*funcptr)(void)){

	i8259aISRCallback = funcptr;
	return;
}

void setMemorySyscallISRCallback(void callback (*funcptr)(reg32b, uint32*, SyscallDescriptor*, SyscallReturnDescriptor*)){

	memorySyscallISRCallback = funcptr;
	return;
}

void setProcSyscallISRCallback(void callback (*funcptr)(reg32b, uint32*, SyscallDescriptor*, SyscallReturnDescriptor*)){

	procSyscallISRCallback = funcptr;
	return;
}


uint32 mips32r2GetProcessorID(void){

	uint32 cpuID;
	
	ReadCP0Value(cpuID, 15, 0);
	
	return cpuID;
}

uint32 mips32r2InterruptService(reg32b cause, uint32* fp, SyscallDescriptor* scd, SyscallReturnDescriptor* scrd){

	reg8b ip = (reg8b)((cause & 0x0000ff00) >> 8);
	reg8b excCode = (reg8b)((cause & 0x0000007c) >> 2);
	
	if (excCode == 0x00){ /*interrupt exception*/
		if ((ip & 0x04) != 0x00){ /*hw int0 - i8259a*/
			i8259aISRCallback();
			return 0;
		} else if ((ip & 0x02) != 0x00){ /*sw int1*/
			sw1ISRCallback();
			return 0;
		} else if ((ip & 0x01) != 0x00){ /*sw int0*/
			sw0ISRCallback();
			return 0;
		} else {
			return 0;
		}	
	} else if (excCode == 0x01){ /*TLB modified exception*/
		return 0;
	} else if ((excCode == 0x02) || (excCode == 0x03)){ /*TLB invalid or TLB refill exception*/
	
		reg32b cp0Context = 0x0;
		reg32b cp0EntryHi = 0x0;
		
		ReadCP0Value(cp0Context, 4, 0);
		ReadCP0Value(cp0EntryHi, 10, 0);
		
		tlbLoadStoreISRCallback(excCode, cp0Context, cp0EntryHi);
		return 0;
	} else if (excCode == 0x08){ /*syscall exception*/
	
		switch (scd->cmd){

			case SYSCMDSYSMALLOC:
			case SYSCMDSYSFREE: /*memory syscall*/
				memorySyscallISRCallback(cause, fp, scd, scrd);
				break;

			case SYSCMDEXECFROMMEM:
			case SYSCMDKILLPROCESS:
				procSyscallISRCallback(cause, fp, scd, scrd);
				break;
				
			default: /*not supported syscall*/
				scrd->ifSuccess = FALSE;
				break;
		};
		return 4;
	} else {
		return 0;
	}
}

